package gameobject;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.RGBImageFilter;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import simulation.Game;

/**
 * Sprite class provides the framework for any
 * characters that are created in the game.
 * If a character has unique movement, key handling,
 * or collision handling, it should overwrite those
 * methods itself.
 *
 */
/**
 * @author Volodymyr, Rex
 *
 */
public class Sprite implements Cloneable {

    private boolean myIsVisible;
    private Image myImage;
    //the sprite can only be displayed within the bound
    private Dimension myBound;
    private double myX;
    private double myY;
    private double myDX;
    private double myDY;
    //for each movement, x+=dx*speed and y+=dy*speed
    private int mySpeed;
    //initial HP when constructed
    private int myMaxHP;
    //hit points
    private int myHP;
    private int myDamageOnCollide;
    private int myWidth;
    private int myHeight;
    private Game myGame;
    private List<Sprite> myOwnSprites;

    /**
     * Constructor for empty Sprite.
     */
    public Sprite() {
        myIsVisible = false;
    }

    /**
     * Constructor for a sprite to be used in the game.
     * @param game the game that is using this sprite
     * @param dx acceleration in x direction
     * @param dy acceleration in y direction
     * @param speed how fast it starts out moving
     * @param hp starting health
     * @param damage starting damage it deals
     */
    public Sprite(Game game, Number dx, Number dy,
            Number speed, Number hp, Number damage) {
        setGame(game);
        myOwnSprites = new ArrayList<Sprite>();
        myIsVisible = true;
        //the coordinate needs to be re-assigned
        this.myDX = dx.doubleValue();
        this.myDY = dy.doubleValue();
        mySpeed = speed.intValue();
        setHP(hp.intValue());
        setDamageOnCollide(damage.intValue());
    }

    /**
     * Creates a sprite.
     * @param game the game that will use the sprite
     * @param args the arguments to pass to the constructor
     * @return
     */
    public Sprite createProduct(Game game, String[] args) {
        Number[] parameters = convertToParameters(args);
        Sprite myProduct = new Sprite(game, parameters[findIndex("dx")],
                parameters[findIndex("dy")], parameters[findIndex("speed")],
                parameters[findIndex("HP")], parameters[findIndex("damage")]);
        myProduct.setImage(args[findIndex("image")]);
        return myProduct;
    }

    protected Number[] convertToParameters(String[] args) {
        Number[] result = new Number[args.length];
        for (int i = 0; i < findIndex("image"); i++) {
            result[i] = Double.parseDouble(args[i]);
        }
        return result;
    }

    /**
     * Makes the sprite invisible.
     */
    public void die() {
        myIsVisible = false;
    }

    /**
     * @return whether the sprite is visible.
     */
    public boolean isVisible() {
        return myIsVisible;
    }

    /**
     * Makes sprite (in)visible.
     * @param visible true or false
     */
    public void setVisible(boolean visible) {
        myIsVisible = visible;
    }

    /**
     * @return the width of the sprite
     */
    public int getWidth() {
        return myWidth;
    }

    /**
     * @return the height of the sprite
     */
    public int getHeight() {
        return myHeight;
    }

    /**
     * Sets the size of the sprite.
     * @param dimension size to set
     */
    public void setDimension(Dimension dimension) {
        myBound = dimension;
    }

    /**
     * @return the sprite's size
     */
    public Dimension getDimension() {
        return myBound;
    }

    /**
     * Sets the picture of the sprite.
     * @param image what the sprite looks like
     */
    public void setImage(Image image) {
        this.myImage = image;
    }

    /**
     * Sets the picture used by the sprite.
     * @param addr the name of the picture
     */
    public void setImage(String addr) {
        ImageIcon ii = new ImageIcon(addr);
        myWidth = ii.getImage().getWidth(null);
        myHeight = ii.getImage().getHeight(null);
        setImage(ii.getImage());
    }

    /**
     * @return the picture of the sprite
     */
    public Image getImage() {
        return myImage;
    }

    /**
     * @return the rectangle encompassing the sprite
     */
    public Rectangle getBounds() {
        return new Rectangle(getX(), getY(), myWidth, myHeight);
    }

    /**
     * @return the speed of the sprite
     */
    public int getSpeed() {
        return mySpeed;
    }

    /**
     * Sets the x position of the sprite.
     * @param x the x position
     */
    public void setX(double x) {
        this.myX = x;
    }

    /**
     * Sets the y position of the sprite.
     * @param y position
     */
    public void setY(double y) {
        this.myY = y;
    }

    /**
     * @return the y position
     */
    public int getY() {
        return (int)myY;
    }

    /**
     * @return the x position
     */
    public int getX() {
        return (int)myX;
    }

    /**
     * Sets the position of the sprite.
     * @param p point to set to
     */
    public void setPosition(Point p) {
        setX(p.x);
        setY(p.y);
    }

    /**
     * Called when the sprite collides.
     * @param impact the damage taken
     */
    public void collide(int impact) {
        myHP -= impact;
        if (myHP <= 0) {
            die();
        }
    }

    /**
     * Called when the sprite collides.
     * @param other the other sprite
     */
    public void collide(Sprite other) {
    }

    /**
     * Adds a sprite to this own collection.
     * @param s sprite to add
     */
    public void addOwnSprite(Sprite s) {
        myOwnSprites.add(s);
    }

    /**
     * @return the list of this sprite's own sprites
     */
    public List<Sprite> getOwnSprites() {
        return myOwnSprites;
    }

    /**
     * @return returns a clear list
     */
    public List<Sprite> flushOwnSprites() {
        myOwnSprites.clear();
        return myOwnSprites;
    }

    /**
     * Fires a missile.
     */
    public void fire() {}

    /**
     * The sprite changes color when its HP is below certain point.
     * A sprite will not be weakened if its initial HP is 0.
     */
    public void weaken() {
        if (getImage() == null) {
            return;
        }
        JFrame j = new JFrame();
        ImageFilter colorFilter = new RedBlueSwapFilter();
        setImage(j.createImage(new FilteredImageSource(
                        getImage().getSource(), colorFilter)));
    }

    /**
     * Fine the index of args, an array of string passed to createProduct[].
     * that contains information about creating a new Sprite
     *
     * @param name name of the field required
     * @return index of args corresponding to name
     */
    protected int findIndex(String name) {
        final String[] myNames = {"dx", "dy", "speed", "HP", "damage", "image"};
        for (int i = 0; i < myNames.length; i++) {
            if (name.equals(myNames[i])) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Paint this sprite.
     * @param g used to paint
     */
    public void paint (Graphics g) {
        if (isVisible()) {
            g.drawImage(getImage(), getX(), getY(), null);
            for (Sprite s : getOwnSprites()) {
                s.paint(g);
            }
        }
    }

    /**
     * Updates this sprite's position based on speed
     * and acceleration.
     */
    public void update() {
        myX += myDX * mySpeed;
        myY += myDY * mySpeed;
        if (myBound != null) {
            checkBounds();
        }
        for (Sprite s : getOwnSprites()) {
            s.update();
        }
    }

    protected void checkBounds() {
        if ((myX > myBound.width) || (myX < 0)) {
            die();
        }
        else if ((myY > myBound.height) || (myY < 0)) {
            die();
        }
    }

    /**
     * Clone this sprite.
     * @throws CloneNotSupportedException if can't clone
     */
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    /**
     * Do nothing if key is pressed.
     * @param e key pressed
     */
    public void handleKeyPressed (KeyEvent e) {
    }

    /**
     * Do nothing if key is released.
     * @param e key released
     */
    public void handleKeyReleased (KeyEvent e) {
    }

    /**
     * @return the damage taken when colliding
     */
    public int getDamageOnCollide() {
        return myDamageOnCollide;
    }

    /**
     * Sets the damage taken on collision.
     * @param damage the amount to be taken
     */
    public void setDamageOnCollide(int damage) {
        myDamageOnCollide = damage;
    }

    /**
     * Sets the sprite's health.
     * @param hp starting health
     */
    public void setHP(int hp) {
        myMaxHP = hp;
        myHP = hp;
    }

    /**
     * @return the max health
     */
    public int getMaxHP() {
        return myMaxHP;
    }

    /**
     * @return the current health
     */
    public int getHP() {
        return myHP;
    }

    /**
     * Sets the x acceleration.
     * @param value of dx
     */
    public void setDx(int value) {
        myDX = value;
    }

    /**
     * Sets the y acceleration.
     * @param value of dy
     */
    public void setDy(int value) {
        myDY = value;
    }

    /**
     * Does nothing?
     */
    public void activate() {
        //do nothing when activated
    }

    /**
     * @return Returns the game this sprite
     * belongs to.
     */
    public Game getGame() {
        return myGame;
    }

    /**
     * Change the game this sprite belongs to.
     * @param game to set it to
     */
    public void setGame (Game game) {
        this.myGame = game;
    }

    /**
     * @param res string
     * @return the resource package
     */
    public String getResource(String res) {
        return getGame().getResource(res);
    }

    /**
     * Used for weakening the Sprite by filtering out colors.
     *
     * @author Rex
     */
    protected class RedBlueSwapFilter extends RGBImageFilter {
        public RedBlueSwapFilter() {
            // The filter's operation does not depend on the
            // pixel's location, so IndexColorModels can be
            // filtered directly.
            canFilterIndexColorModel = true;
        }

        //filter out green color; the sprite is weakened!
        public int filterRGB(int x, int y, int rgb) {
            return rgb & 0xffffffff
                | (rgb & 0xff0000) >> 16
                | (rgb & 0xff) << 16;
        }
    }
}
